package com.example.demo.io;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;

public class MultiplexingSocket {
    static ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
    protected static void readDataFromSocket(SelectionKey key) throws Exception{
        SocketChannel socketChannel = (SocketChannel) key.channel();
        // 非阻塞, >0 表示读取到的字节数量, 0或-1表示未读取到或读取异常
        // 请注意：这个例子降低复杂度，不考虑报文大于buffer size的情况
        int num = socketChannel.read(buffer);
        if(num > 0){
            buffer.flip();
            byte[]  clientBytes =  new byte[buffer.limit()];
            buffer.get(clientBytes);
            System.out.println(socketChannel.socket().getPort() + ":" + new String(clientBytes));
            //清空缓冲区
            buffer.clear();
        }
    }
    public static void main(String args[]) throws Exception{
        LinkedList<SocketChannel> clients = new LinkedList<>();
        //1.启动server
        //new socket,开启监听
        ServerSocketChannel socketChannel = ServerSocketChannel.open();
        socketChannel.bind(new InetSocketAddress(9090));
        //设置非阻塞，接受客户端
        socketChannel.configureBlocking(false);
        //多路复用器（JDK包装的代理，select /poll/epoll/kqueue）
        Selector selector = Selector.open(); //java自动代理，默认为epoll
        //Selector selector = PollSelectorProvider.provider().openSelector();//指定为poll

        //将服务端socket 注册到 多路复用器
        socketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //2. 轮训多路复用器
        // 先询问有没有连接,如果有则返回数量以及对应的对象(fd)
        while (selector.select() > 0){
            System.out.print("");
            Set<SelectionKey> selectionKeySet = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeySet.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();

                //2.1 处理新的连接
                if(key.isAcceptable()){
                    SocketChannel client = socketChannel.accept();
                    client.configureBlocking(false);

                    //同样，把client也注册到selector
                    client.register(selector,SelectionKey.OP_READ);
                    System.out.println("new client : " + client.getRemoteAddress());
                }else if(key.isReadable()){
                    readDataFromSocket(key);
                }
            }
        }
    }
}


/**
 *
 * 优点
 *
 * 线程数同样很少，甚至可以把acceptor线程和worker线程使用同一个。
 *
 *
 * 时间复杂度低，Java实现的Selector（在Linux OS下使用的epoll函数）支持多个clientChannel事件的一次性获取，且时间复杂度维持在O(1)。
 *
 *
 * CPU使用低：得益于Selector，我们不用向 “2.NIO”中需要自己一个个ClientChannel手动去检查事件，因此使得CPU使用率大大降低。
 *
 *
 * 缺点
 *
 * 数据处理麻烦：目前socketChannel.read 读取数据完全是基于字节的，当我们需要需要作为HTTP服务网关时，对于HTTP协议的处理完全需要自己解析，
 * 这是个庞大、烦杂、容易出错的工作。
 *
 *
 * 性能
 *
 *
 * 现有socket数据的读取（socketChannel.read(buffer)）全部通过一个buffer 缓冲区来接受，一旦连接多起来，这无疑是一个单线程读取，性能无疑是个问题。
 *
 *
 * 那么此时buffer我们每次读取都重新new出来呢？如果每次都new出来，这样的内存碎片对于GC无疑是一场灾难。如何平衡地协调好buffer的共享，既保证性能，
 * 又保证线程安全，这是个难题。
 */